/*

    Support for Destinator POI's, Itineraries and Tracklogs.
    ( as described at "http://mozoft.com/d3log.html" )
    
    Copyright (C) 2008 Olaf Klein, o.b.klein@gpsbabel.org


    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA

 */

#include "defs.h"
#include "cet.h"
#include "cet_util.h"
#include "garmin_fs.h"
#include "strptime.h"
#include <ctype.h>
#include <time.h>

#define MYNAME 		"destinator"
#define DST_DYN_POI 	"Dynamic POI"
#define DST_ITINERARY 	"City->Street"

static
arglist_t destinator_args[] = {
	ARG_TERMINATOR
};

static gbfile *fin, *fout;
static gpsdata_type data_type;


/*******************************************************************************/
/*                                   READER                                    */
/*-----------------------------------------------------------------------------*/

static garmin_fs_t *
gmsd_init(waypoint *wpt)
{
	garmin_fs_t *gmsd = GMSD_FIND(wpt);
	if (gmsd == NULL) {
		gmsd = garmin_fs_alloc(-1);
		fs_chain_add(&wpt->fs, (format_specific_data *) gmsd);
	}
	return gmsd;
}

static char *
read_wcstr(const int discard)
{
	gbint16 *buff = NULL, c;
	int size = 0, pos = 0;
	
	while (gbfread(&c, sizeof(c), 1, fin) && (c != 0)) {
		if (size == 0) {
			size = 16;
			buff = xmalloc(size * sizeof(*buff));
		}
		else if (pos == size) {
			size += 16;
			buff = xrealloc(buff, size * sizeof(*buff));
		}
		buff[pos] = c;
		pos += 1;
	}

	if (pos != 0) {
		char *res;
		if (discard) res = NULL;
		else {
			res = cet_str_uni_to_utf8(buff, pos);
			res = lrtrim(res);
			if (*res == '\0') {
				xfree(res);
				res = NULL;
			}
		}
		xfree(buff);
		return res;
	}
	else
		return NULL;
}

static void
write_wcstr(const char *str)
{
	int len;
	short *unicode;
	
	unicode = cet_str_utf8_to_uni(str, &len);
	gbfwrite((void *)unicode, 2, len + 1, fout);
	xfree(unicode);
}

static int
read_until_wcstr(const char *str)
{
	char *buff;
	int len, sz;
	int eos = 0, res = 0;
	
	len = strlen(str);
	sz = (len + 1) * 2;
	buff = xcalloc(sz, 1);
	
	while (! gbfeof(fin)) {

		char c = gbfgetc(fin);
		memmove(buff, buff + 1, sz - 1);
		buff[sz - 1] = c;

		if (c == 0) {
			eos++;
			if (eos >= 2) {	/* two or more zero bytes => end of string */
				char *test = cet_str_uni_to_utf8((short *)buff, len);
				if (test) {
					res = (strcmp(str, test) == 0);
					xfree(test);
					if (res) break;
				}
			}
		}
		else eos = 0;
	}
	xfree(buff);
	return res;
}

static void
destinator_read_poi(void)
{
	waypoint *wpt;
	int count = 0;
	
	gbfrewind(fin);
	
	while (! (gbfeof(fin))) {
		char *str, *hnum;
		double ll;
		garmin_fs_t *gmsd;
		
		if (count == 0) {
			str = read_wcstr(0);
			if ((str == NULL) || (strcmp(str, DST_DYN_POI) != 0))
				fatal(MYNAME "_poi: Invalid record header!\n");
			xfree(str);
		}
		else if (! read_until_wcstr(DST_DYN_POI)) break;

		count++;
			
		wpt = waypt_new();
		
		wpt->shortname = read_wcstr(0);
		wpt->notes = read_wcstr(0);		/* comment */

		hnum = read_wcstr(0);			/* house number */

		str = read_wcstr(0); 			/* street */
		if (!str) {
			str = hnum;
			hnum = NULL;
		}
		if (str) {
			gmsd = gmsd_init(wpt);
			if (hnum) {
				str = xstrappend(str, " ");
				str = xstrappend(str, hnum);
			}
			GMSD_SET(addr, str);
		}

		if ((str = read_wcstr(0))) {		/* city */
			gmsd = gmsd_init(wpt);
			GMSD_SET(city, str);
		}

		if (hnum) xfree(hnum);

		(void) read_wcstr(1);			/* unknown */

		if ((str = read_wcstr(0))) {		/* postcode */
			gmsd = gmsd_init(wpt);
			GMSD_SET(postal_code, str);
		}

		(void) read_wcstr(1);			/* unknown */

		(void) gbfgetdbl(fin);
		
		wpt->longitude = gbfgetdbl(fin);
		wpt->latitude = gbfgetdbl(fin);
		ll = gbfgetdbl(fin);
		if (ll != wpt->longitude)
			fatal(MYNAME "_poi: Invalid file!\n");
		ll = gbfgetdbl(fin);
		if (ll != wpt->latitude)
			fatal(MYNAME "_poi: Invalid file!\n");
		
		waypt_add(wpt);
	}
}

static void
destinator_read_rte(void)
{
	int count = 0;
	route_head *rte = NULL;

	gbfrewind(fin);
	
	while (! (gbfeof(fin))) {
		char *str;
		waypoint *wpt;
		
		if (count == 0) {
			str = read_wcstr(0);
			if ((str == NULL) || (strcmp(str, DST_ITINERARY) != 0))
				fatal(MYNAME "_itn: Invalid record header!\n");
			xfree(str);
		}
		else if (! read_until_wcstr(DST_ITINERARY)) break;

		count++;
		
		wpt = waypt_new();

		wpt->shortname = read_wcstr(0);
		wpt->notes = read_wcstr(0);

		(void) gbfgetint32(fin);
		(void) gbfgetdbl(fin);
		(void) gbfgetdbl(fin);

		wpt->longitude = gbfgetdbl(fin);
		wpt->latitude = gbfgetdbl(fin);
		if (gbfgetdbl(fin) != wpt->longitude)
			fatal(MYNAME "_itn: Invalid file!\n");
		if (gbfgetdbl(fin) != wpt->latitude)
			fatal(MYNAME "_itn: Invalid file!\n");
		
		if (! rte) {
			rte = route_head_alloc();
			route_add_head(rte);
		}
		route_add_wpt(rte, wpt);

		(void) gbfgetdbl(fin);
		(void) gbfgetdbl(fin);
	}
}

static void
destinator_read_trk(void)
{
	char TXT[4] = "TXT";
	int recno = -1;
	route_head *trk = NULL;
	
	gbfrewind(fin);

	while (! (gbfeof(fin))) {
		waypoint *wpt;
		struct tm tm;
		char buff[20];
		int date;
		double time;
		
		recno++;
		
		if (gbfeof(fin)) break;
		
		wpt = waypt_new();

		wpt->longitude = gbfgetdbl(fin);
		wpt->latitude = gbfgetdbl(fin);
		wpt->altitude = gbfgetdbl(fin);

		(void) gbfgetdbl(fin);				/* unknown */
		(void) gbfgetdbl(fin);				/* unknown */
		(void) gbfgetdbl(fin);				/* unknown */

		wpt->fix = gbfgetint32(fin);
		wpt->sat = gbfgetint32(fin);

		gbfseek(fin, 12 * sizeof(gbint32), SEEK_CUR);	/* SAT info */

		date = gbfgetint32(fin);
		time = gbfgetflt(fin);

		gbfseek(fin, 2 * 12, SEEK_CUR);			/* SAT info */

		gbfread(TXT, 1, 3, fin);
		if (strcmp(TXT, "TXT") != 0)
			fatal(MYNAME "_trk: No (or unknown) file!\n");

		gbfseek(fin, 13, SEEK_CUR);			/* unknown */

		memset(&tm, 0, sizeof(tm));
			
		snprintf(buff, sizeof(buff), "%06d%.f", date, time);
		strptime(buff, "%d%m%y%H%M%S", &tm);
		wpt->creation_time = mkgmtime(&tm);
		wpt->microseconds = ((int)time % 1000) * 1000;
			
		if (wpt->fix > 0) wpt->fix++;

		if (! trk) {
			trk = route_head_alloc();
			track_add_head(trk);
		}
		track_add_wpt(trk, wpt);
	}
}

static void
destinator_read(void)
{
	int i0, i1;
	double d0, d1;
	char buff[16];
	
	if (! gbfread(buff, 1, sizeof(buff), fin))
		fatal(MYNAME ": Unexpected EOF (end of file)!\n");

	i0 = le_read32(&buff[0]);
	i1 = le_read32(&buff[4]);
	
	if ((i0 == 0x690043) && (i1 == 0x790074)) {
		if (data_type != rtedata)
			warning(MYNAME ": Using Destinator Itinerary Format!\n");
		destinator_read_rte();
	}
	else if ((i0 == 0x790044) && (i1 == 0x61006e)) {
		if (data_type != wptdata)
			warning(MYNAME ": Using Destinator POI Format!\n");
		destinator_read_poi();
	}
	else {
		if (data_type != trkdata)
			warning(MYNAME ": Using Destinator Tracklog Format!\n");
		
		le_read64(&d0, &buff[0]);
		le_read64(&d1, &buff[8]);
		if ((fabs(d0) > 180) || (fabs(d1) > 90))
			fatal(MYNAME ": No Destinator (.dat) file!\n");
		destinator_read_trk();
	}
}

/*******************************************************************************/
/*                                   WRITER                                    */
/*-----------------------------------------------------------------------------*/

static void
destinator_wpt_disp(const waypoint *wpt)
{
	garmin_fs_t *gmsd = GMSD_FIND(wpt);

	write_wcstr(DST_DYN_POI);
	write_wcstr((wpt->shortname) ? wpt->shortname : "WPT");
	write_wcstr((wpt->notes) ? wpt->notes : wpt->description);

	write_wcstr(NULL);				/* house number */
	write_wcstr(GMSD_GET(addr, NULL));		/* street */
	write_wcstr(GMSD_GET(city, NULL));		/* city */
	write_wcstr(NULL);				/* unknown */
	write_wcstr(GMSD_GET(postal_code, NULL));	/* postcode */
	write_wcstr(NULL);				/* unknown */

	gbfputint32(0, fout);
	gbfputint32(0, fout);

	gbfputdbl(wpt->longitude, fout);
	gbfputdbl(wpt->latitude, fout);
	gbfputdbl(wpt->longitude, fout);
	gbfputdbl(wpt->latitude, fout);

	gbfputdbl(0, fout);
	gbfputdbl(0, fout);
}

static void
destinator_trkpt_disp(const waypoint *wpt)
{
	int i;
	
	gbfputdbl(wpt->longitude, fout);
	gbfputdbl(wpt->latitude, fout);
	gbfputdbl(wpt->altitude, fout);
	gbfputdbl(0, fout);
	gbfputdbl(0, fout);
	gbfputdbl(0, fout);
	gbfputint32(wpt->fix > fix_unknown ? wpt->fix - 1 : 0, fout);
	gbfputint32(wpt->sat, fout);
	for (i = 0; i < 12; i++) gbfputint32(0, fout);
	
	if (wpt->creation_time) {
		struct tm tm;
		double time;
		int date;

		tm = *gmtime(&wpt->creation_time);
		tm.tm_mon += 1;
		tm.tm_year -= 100;
		date = ((int)tm.tm_mday * 10000) + ((int)tm.tm_mon * 100) + tm.tm_year;
		gbfputint32(date, fout);
		
		time = ((int)tm.tm_hour * 10000) + ((int)tm.tm_min * 100) + tm.tm_sec;
		time = (time * 1000) + (wpt->microseconds / 1000);
		gbfputflt(time, fout);
	}
	else {
		gbfputint32(0, fout);	/* Is this invalid ? */
		gbfputflt(0, fout);
	}

	for (i = 0; i < 12; i++) gbfputint16(0, fout);
	gbfputcstr("TXT", fout);
	for (i = 0; i < 12; i++) gbfputc(0, fout);
}

static void
destinator_rtept_disp(const waypoint *wpt)
{
	write_wcstr(DST_ITINERARY);
	write_wcstr((wpt->shortname) ? wpt->shortname : "RTEPT");
	write_wcstr((wpt->notes) ? wpt->notes : wpt->description);

	gbfputint32(0, fout);
	gbfputdbl(0, fout);
	gbfputdbl(0, fout);

	gbfputdbl(wpt->longitude, fout);
	gbfputdbl(wpt->latitude, fout);
	gbfputdbl(wpt->longitude, fout);
	gbfputdbl(wpt->latitude, fout);

	gbfputdbl(0, fout);
	gbfputdbl(0, fout);
}

/*******************************************************************************
* %%%        global callbacks called by gpsbabel main process              %%% *
*******************************************************************************/

static void
destinator_rd_init(const char *fname)
{
	fin = gbfopen_le(fname, "rb", MYNAME);
}

static void 
destinator_rd_deinit(void)
{
	gbfclose(fin);
}

static void
destinator_read_poi_wrapper(void)
{
	data_type = wptdata;
	destinator_read();
}

static void
destinator_read_rte_wrapper(void)
{
	data_type = rtedata;
	destinator_read();
}

static void
destinator_read_trk_wrapper(void)
{
	data_type = trkdata;
	destinator_read();
}

static void
destinator_wr_init(const char *fname)
{
	fout = gbfopen_le(fname, "wb", MYNAME);
}

static void
destinator_wr_deinit(void)
{
	gbfclose(fout);
}

static void
destinator_write_poi(void)
{
	waypt_disp_all(destinator_wpt_disp);
}

static void
destinator_write_rte(void)
{
	route_disp_all(NULL, NULL, destinator_rtept_disp);
}

static void
destinator_write_trk(void)
{
	track_disp_all(NULL, NULL, destinator_trkpt_disp);
}

/**************************************************************************/

ff_vecs_t destinator_poi_vecs = {
	ff_type_file,
	{ 
	  	ff_cap_read | ff_cap_write	/* waypoints */, 
		ff_cap_none		 	/* tracks */, 
	  	ff_cap_none 			/* routes */
	},
	destinator_rd_init,	
	destinator_wr_init,	
	destinator_rd_deinit,	
	destinator_wr_deinit,	
	destinator_read_poi_wrapper,
	destinator_write_poi,
	NULL,
	destinator_args,
	CET_CHARSET_UTF8, 1			/* fixed */
};

ff_vecs_t destinator_itn_vecs = {
	ff_type_file,
	{ 
	  	ff_cap_none 			/* waypoints */, 
		ff_cap_none		 	/* tracks */, 
	  	ff_cap_read | ff_cap_write	/* routes */
	},
	destinator_rd_init,	
	destinator_wr_init,	
	destinator_rd_deinit,	
	destinator_wr_deinit,	
	destinator_read_rte_wrapper,
	destinator_write_rte,
	NULL,
	destinator_args,
	CET_CHARSET_UTF8, 1			/* fixed */
};

ff_vecs_t destinator_trl_vecs = {
	ff_type_file,
	{ 
	  	ff_cap_none 			/* waypoints */, 
		ff_cap_read | ff_cap_write 	/* tracks */, 
	  	ff_cap_none 			/* routes */
	},
	destinator_rd_init,	
	destinator_wr_init,	
	destinator_rd_deinit,	
	destinator_wr_deinit,	
	destinator_read_trk_wrapper,
	destinator_write_trk,
	NULL,
	destinator_args,
	CET_CHARSET_UTF8, 1			/* fixed */
};

/**************************************************************************/
